package com.caishi.lkx.order.service.impl;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import com.alibaba.fastjson.JSON;
import com.caishi.lkx.common.ienum.DeviceType;
import com.caishi.lkx.distribution.model.DistributionCommissionRecords;
import com.caishi.lkx.distribution.model.DistributionMoneyLevel;
import com.caishi.lkx.distribution.service.ICommissionRecordsService;
import com.caishi.lkx.distribution.service.IMoneyLevelService;
import com.caishi.lkx.order.service.*;
import com.caishi.lkx.user.model.DistributionUser;
import com.caishi.lkx.user.service.IDistributionUserService;
import com.zzw.common.Wrappers;
import com.zzw.common.entity.IBaseMapper;
import com.zzw.common.exception.BizRuntimeException;
import com.zzw.common.lock.RedissonLock;
import com.zzw.common.utils.TimeUtil;
import com.caishi.lkx.order.OrderResultCode;
import com.caishi.lkx.order.ienum.status.OrderStatus;
import com.caishi.lkx.order.ienum.status.PaymentStatus;
import com.caishi.lkx.order.ienum.type.PaymentType;
import com.caishi.lkx.order.mapper.PaymentMapper;
import com.caishi.lkx.order.model.OrderDetailModel;
import com.caishi.lkx.order.model.OrderModel;
import com.caishi.lkx.order.model.PaymentModel;
import com.caishi.lkx.order.model.PaymentRefundLogModel;
import com.caishi.lkx.order.pay.PayFactory;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeoutException;

/**
 * @author by keray
 * date:2021/7/14 4:51 下午
 */
@Service
@Slf4j
public class PaymentServiceImpl implements IPaymentService {

    @Resource
    private PaymentMapper paymentMapper;

    @Resource
    private OrderServiceImpl orderService;

    @Resource
    private IPayImageLogService payImageLogService;

    @Resource
    private IPaymentRefundLogService paymentRefundLogService;

    @Resource
    private IUserAssetsDataService assetsDataService;

    @Resource
    private IOrderDetailService orderDetailService;
    @Resource
    private IDistributionUserService distributionUserService;
    @Resource
    private IMoneyLevelService moneyLevelService;
    @Resource
    private ICommissionRecordsService commissionRecordsService;

    @Override
    public IBaseMapper<PaymentModel, Long> getMapper() {
        return paymentMapper;
    }

    @Override
    public PaymentModel createPayment(OrderModel orderModel, PaymentType paymentType) {
        Assert.notEmpty(orderModel.getId(), "订单Id不存在");
        return PaymentModel.builder()
                .orderId(orderModel.getId())
                .paymentStatus(PaymentStatus.waitPay)
                .paymentAmount(orderModel.getPrice())
                .paymentType(paymentType)
                .payUserId(orderModel.getUserId())
                .build();
    }

    @Override
    public PaymentModel getPayModel(String orderId) {
        return paymentMapper.selectFirst(Wrappers.<PaymentModel>lambdaQuery().eq(PaymentModel::getOrderId, orderId));
    }

    @SneakyThrows
    @Override
    public Map<String, Object> getPaySign(String currentUser, PaymentType paymentType, String orderId, DeviceType deviceType, Object val, Map<String, Object> otherParam) {
        OrderModel orderModel = orderService.getById(orderId);
        if (orderModel.getOrderStatus() == OrderStatus.expired) {
            throw new BizRuntimeException("订单已过期");
        } else if (orderModel.getOrderStatus() == OrderStatus.delete) {
            throw new BizRuntimeException("订单已删除");
        } else if (orderModel.getOrderStatus() == OrderStatus.cancel) {
            throw new BizRuntimeException("订单已取消");
        } else if (orderModel.getOrderStatus() != OrderStatus.waitPay) {
            throw new BizRuntimeException("订单已支付");
        }
        OrderModel orderModelDetail = orderService.orderDetail(orderModel, true, true, false);
        String userId = orderModelDetail.getUserId();
        orderModelDetail.getOrderDetail().forEach(f -> {
            Long userAssetId = f.getUserAssetId();
            if (assetsDataService.userHaveAsset(userId, f.getEntityType(), String.valueOf(userAssetId))) {
                throw new BizRuntimeException("商品已购买");
            }
        });
        List<Long> orderDetailIds = orderModelDetail.getOrderDetail().stream().map(OrderDetailModel::getId).toList();
        if (orderModel.getPrice() == 0) paymentType = PaymentType.free;
        PaymentType finalPaymentType = paymentType;
        PaymentModel paymentModel = RedissonLock.lock("", () -> {
            var p = getPayModel(orderId);
            if (p == null) {
                p = createPayment(orderModel, finalPaymentType);
            }
            return p;
        });
        //解决微信支付不同平台的商品号重复
        if (paymentModel.getMode() != null && !paymentModel.getMode().equals(null == val ? null : val.toString()) && paymentType.equals(PaymentType.weixin)) {
            String generateOrderSn = orderService.generateOrderSn(orderModel);
            orderService.delete(orderModel.getId());
            orderModel.setId(generateOrderSn);
            orderService.insert(orderModel);
            orderDetailService.update(null, Wrappers.<OrderDetailModel>lambdaUpdate()
                    .in(OrderDetailModel::getId, orderDetailIds)
                    .set(OrderDetailModel::getOrderId, generateOrderSn)
            );
            paymentModel.setOrderId(generateOrderSn);
        }
        paymentModel.setMode(null == val ? null : val.toString());
        paymentModel.setPayUserId(currentUser);
        paymentModel.setDeviceType(deviceType);
        paymentModel.setPaymentType(paymentType);
        if (paymentModel.getId() == null) {
            owner(PaymentServiceImpl.class).insert(paymentModel);
        } else {
            owner(PaymentServiceImpl.class).update(paymentModel);
        }
        Map<PaymentType, String> protocol = MapUtil.builder(PaymentType.alipay, "alipay://").put(PaymentType.weixin, "weixin://").build();
        return MapUtil.<String, Object>builder()
                .put("paymentType", paymentType)
                .put("payProtocol", protocol.get(paymentType))
                .putAll(Objects.requireNonNull(PayFactory.create(paymentType)).sign(paymentModel, val, otherParam))
                .build();
    }

    @Override
    public void paySuccess(String orderId, Object image,String recommend) {

        try {
            if (!checkOrderPay(orderId)) {
                throw new BizRuntimeException(OrderResultCode.orderNotPay);
            }
            RLock lock = RedissonLock.lock("paySuccess::" + orderId);
            if (TransactionSynchronizationManager.isSynchronizationActive()) {
                lock.lock();
                TransactionSynchronizationManager.registerSynchronization(
                        new TransactionSynchronization() {
                            @Override
                            public void afterCompletion(int status) {
                                lock.unlock();
                            }
                        }
                );
            } else {
                throw new BizRuntimeException("无事务");
            }
            log.info("订单支付完成 orderId={},time={}", orderId, TimeUtil.nowMS());
            PaymentModel paymentModel = getPayModel(orderId);
            if (paymentModel == null) {
                log.error("paymentModel is null orderId={}", orderId);
                throw new BizRuntimeException();
            }

            // 直接返回仍然回调应答了呀
            if (paymentModel.getPaymentStatus() == PaymentStatus.paySuccess) {
                log.warn("订单已经完成，无需重复执行");
                return;
            }

            // 处理订单完成前钩子
            if (PayFactory.create(paymentModel.getPaymentType()).prePayCallBack(paymentModel)) {
                orderService.orderPaySuccess(orderId);
                paymentModel.setPaymentStatus(PaymentStatus.paySuccess);
                payImageLogService.addLog(paymentModel, image);
                paymentModel.setPayTime(LocalDateTime.now());
                orderService.orderDelivery(orderId);
                // 支付单完成钩子
                PayFactory.create(paymentModel.getPaymentType()).postPayCallBack(paymentModel);
                this.update(paymentModel);

                // 可能有已支付完成的订单
                // 完成的支付分销记录入库，退款是即时的，没有填退款回调地址，distribution_commission_records
                // 佣金记录表插入支付完成订单对应佣金记录
                // 获取订单对应推荐人，如果没有推荐人，不执行操作
                if(recommend != null){
                    // 获取对应的所有父级（反向递归向上查询）
                    List<DistributionUser> users =  distributionUserService.getRecursiveUser(recommend);
                    // 插入佣金表
                    List<DistributionMoneyLevel> list = moneyLevelService.list();
                    ArrayList<DistributionCommissionRecords> recordsList = new ArrayList<>();
                    for (int i = 0; i < users.size(); i++){
                        recordsList.add(DistributionCommissionRecords.builder()
                                .orderId(paymentModel.getOrderId())
                                .fromUserId(paymentModel.getPayUserId())
                                .userId(users.get(i).getId())
                                .moneyLevelId(list.get(i).getId())
                                .distributionMoney((long)(list.get(i).getCommissionRate()*paymentModel.getPaymentAmount()))
                                .amount(paymentModel.getPaymentAmount())
                                .deleted(false)
                                .build());
                    }
                    commissionRecordsService.saveBatch(recordsList);
                }
            } else {
                log.warn("支付回调完成订单预执行失败 orderId:{} payType={}", orderId, paymentModel.getPaymentType());
                throw new BizRuntimeException(OrderResultCode.orderNotPay);
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    @Override
    public boolean checkOrderPay(String orderId) {
        PaymentModel paymentModel = this.getPayModel(orderId);
        if (paymentModel.getPaymentStatus() == PaymentStatus.paySuccess) {
            return true;
        }
        return PayFactory.create(paymentModel.getPaymentType()).prePayCallBack(paymentModel);
    }

    @Override
    public void payRefund(OrderModel orderModel, Long refundAmount) {
        try {
            RedissonLock.lock("orderRefund::" + orderModel.getId(), (key) -> {
                log.info("订单退款审核过，开始退款 orderId={}", orderModel.getId());
                PaymentModel paymentModel = getPayModel(orderModel.getId());
                if (paymentModel == null) {
                    // 理论上不可能执行这里
                    throw new BizRuntimeException();
                }
                // 如果没设置退款金额或者退款金额大于支付金额就全额退款
                final long finalRefundAmount;
                if (refundAmount == null) {
                    finalRefundAmount = paymentModel.getPaymentAmount();
                } else {
                    finalRefundAmount = refundAmount;
                }
                if (finalRefundAmount > paymentModel.getPaymentAmount()) {
                    throw new BizRuntimeException();
                }
                // 生成退款记录
                PaymentRefundLogModel refundLogModel = paymentRefundLogService.addLog(paymentModel, finalRefundAmount);
                if (!PayFactory.create(paymentModel.getPaymentType()).refund(paymentModel, refundLogModel)) {
                    log.warn("支付单退款失败 data={}", JSON.toJSON(paymentModel));
                    throw new BizRuntimeException(OrderResultCode.orderRefundFail);
                }
                log.info("支付单退款成功 paymentId={}", paymentModel.getId());
                //发送退款成功的消息

            });
        } catch (TimeoutException | InterruptedException e) {
            log.error("退款分布式异常", e);
            throw new BizRuntimeException(e);
        }
    }

}
